# -*- coding: utf-8 -*-
#
# Copyright 2009 Vanderbilt University
# 
# Licensed under the Apache License, Version 2.0 (the "License"); 
# you may not use this file except in compliance with the License. 
# You may obtain a copy of the License at 
# 
#     http://www.apache.org/licenses/LICENSE-2.0 
# 
# Unless required by applicable law or agreed to in writing, software 
# distributed under the License is distributed on an "AS IS" BASIS, 
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
# See the License for the specific language governing permissions and 
# limitations under the License. 

"""Set of plugins for conducting association rule mining on a relational 
network.

.. moduleauthor:: John Paulett <john.paulett -at- vanderbilt.edu>
"""
from __future__ import division
from collections import defaultdict
from hornet.event import GraphsReady
from hornet.file import write_csv
from hornet.plugin import Plugin, log
from hornet.util import extract_attr
import logging
import numpy

logger = logging.getLogger(__name__)

class RuleMiner(Plugin):
    def accepted_events(self):
        return GraphsReady

    @log
    def notify(self, event):
        master = combine_graphs(event.data)
        logger.debug('Combined all graphs')
        write_csv(self.output('rules_%s.csv' % self.id), 
                  rule_result_table(master), 
                  ['ante', 'conseq', 'mean conditional', 'std conditional',
                   'mean joint', 'std joint', 'count']) 
                   #,'conditional inverse ratio', 'joint inverse ratio'  
    
def combine_graphs(graphs):
    """Combine the EdgeDetails into 
    :cls:`hornet.contrib.ruleminer.EdgesResult` (which is a list wrapper) for 
    each unique edge in the graphs.
    Returns a dictionary keyed by the edge tuple and with values as the
    list of :cls:`hornet.network.EdgeDetail` for the edge from all the graphs.  
    Edges that  appear in multiple graphs will have multiple EdgeDetails, 
    whereas edges only in a single graph will only have a single EdgeDetail 
    in the list. 
    """
    details = defaultdict(EdgesResult)
    for graph in graphs:
        for edge in graph.edges_iter(data=True):
            details[edge[0:2]].append(edge[2])
        logger.debug('Got unique edges for graph %s' % (str(graph.name)))
    return dict(details)

class EdgesResult(list):
    """Object for holding the all the instances of 
    :cls:`hornet.network.EdgeDetail` for a given edge. Provides the ability
    to conduct basic statistical characterizations of these instances.
    """
    
    def mean(self, attr):
        return numpy.mean(extract_attr(self, attr))
    
    def std(self, attr):
        return numpy.std(extract_attr(self, attr))
        
def rule_result_table(edge_results):
    table = []
    for edge, result in edge_results.iteritems():
        line = [edge[0], edge[1]]
        
        conditional = result.mean('conditional')
        line.append(conditional)
        line.append(result.std('conditional'))
        
        joint = result.mean('joint')
        line.append(joint)
        line.append(result.std('joint'))
        line.append(len(result))
        
#        try:
#            other_result =  edge_results[(edge[1], edge[0])]
#            conditional_ratio = conditional / other_result.mean('conditional')
#            joint_ratio = joint / other_result.mean('joint')
#        except KeyError:
#            ## the opposite side of the relationships does not exist,
#            conditional_ratio = float('inf')
#            joint_ratio = float('inf')
#        finally:
#            line.append(conditional_ratio)
#            line.append(joint_ratio)
            
        table.append(line)
    return table
        
